﻿using Devil;
using GameToolkit;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace DynamicBone
{
    public class DynamicBoneRoot : MonoBehaviour, ICullingAction
    {
        [SerializeField]
        Space m_SimulateSpace;

        public Transform m_Root;

        [ContextMenuItem("Update Avatar Data", "UpdateAvatarData")]
        public DynamicBoneAvatar m_Avatar;

        public float m_UpdateRate = 60f;

        public bool m_UnscaleTime = false;

#if UNITY_5_3_OR_NEWER
        [Tooltip("How much the bones slowed down.")]
#endif
        [Range(0, 1)]
        public float m_Damping = 0.1f;
        public AnimationCurve m_DampingDistrib = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("How much the force applied to return each bone to original orientation.")]
#endif
        [Range(0, 1)]
        public float m_Elasticity = 0.1f;
        public AnimationCurve m_ElasticityDistrib = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("How much bone's original orientation are preserved.")]
#endif
        [Range(0, 1)]
        public float m_Stiffness = 0.1f;
        public AnimationCurve m_StiffnessDistrib = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("How much character's position change is ignored in physics simulation.")]
#endif
        [Range(0, 1)]
        public float m_Inert = 0;
        public AnimationCurve m_InertDistrib = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("How much the bones slowed down when collide.")]
#endif
        public float m_Friction = 0;
        public AnimationCurve m_FrictionDistrib = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("Each bone can be a sphere to collide with colliders. Radius describe sphere's size.")]
#endif
        public float m_Radius = 0.01f;
        public AnimationCurve m_RadiusDistrib = null;

        [Range(0, 1), Tooltip("Apply world force's weight")]
        public float m_WorldForce = 1;
        //public AnimationCurve m_WorldForceDistrib = null;

        //#if UNITY_5_3_OR_NEWER
        //    [Tooltip("The force apply to bones. Partial force apply to character's initial pose is cancelled out.")]
        //#endif
        //    public Vector3 m_Gravity = Vector3.zero;

#if UNITY_5_3_OR_NEWER
        [Tooltip("The force apply to bones.")]
#endif
        public Vector3 m_Force = Vector3.zero;

#if UNITY_5_3_OR_NEWER
        [Tooltip("Collider objects interact with the bones.")]
#endif
        public List<DynamicBoneColliderBase> m_Colliders = null;

#if UNITY_5_3_OR_NEWER
        [Tooltip("Bones exclude from physics simulation.")]
#endif
        public List<Transform> m_Exclusions = null;

        public enum FreezeAxis
        {
            None, X, Y, Z
        }
#if UNITY_5_3_OR_NEWER
        [Tooltip("Constrain bones to move on specified plane.")]
#endif
        public FreezeAxis m_FreezeAxis = FreezeAxis.None;


        NativeDynamicBoneRoot mNativeInst;
        bool mUpdateBone;

        public Space SimulateSpace
        {
            get
            {
                return m_SimulateSpace;
            }
            set
            {
                if (m_SimulateSpace != value)
                {
                    m_SimulateSpace = value;
                    if (mNativeInst != null)
                        DynamicBoneJobs.MarkRootDirty(mNativeInst);
                }
            }
        }

        private void OnEnable()
        {
            mNativeInst = DynamicBoneJobs.AddRootBone(this);
            if (mUpdateBone && mNativeInst != null)
                DynamicBoneJobs.AddUpdateData(mNativeInst);
        }

        private void OnDisable()
        {
            if (mNativeInst != null)
            {
                mNativeInst.Reset();
                DynamicBoneJobs.RemoveRootBone(mNativeInst);
                mNativeInst = null;
            }
#if UNITY_EDITOR
            m_Particles.Clear();
#endif
        }

        public void OnCulling(CullingCamera culler, bool visible, int distanceLv)
        {
            var update = visible && distanceLv < 3 || distanceLv < 1;
            if (update != mUpdateBone && mNativeInst != null)
            {
                if (update)
                    DynamicBoneJobs.AddUpdateData(mNativeInst);
                else
                    DynamicBoneJobs.RemoveUpdateData(mNativeInst);
            }
            mUpdateBone = update;
        }

#if UNITY_EDITOR

        List<DynamicBoneAvatar.CachedParticle> m_Particles = new List<DynamicBoneAvatar.CachedParticle>();

        [ContextMenu("Update DynamicBoneAvatar Asset")]
        void UpdateAvatarData()
        {
            string createFileAt = null;
            if (m_Avatar == null)
            {
                var pref = GlobalUtil.GetPrefabPath(gameObject);
                var folder = string.IsNullOrEmpty(pref) ? "Assets" : NativeFileSystem.GetFolderName(pref);
                createFileAt = UnityEditor.EditorUtility.SaveFilePanelInProject("Create DynamicBoneAvatar", name, "asset",
                    "Create DynamicBoneAvatar Asset.", folder);
                if (string.IsNullOrEmpty(createFileAt))
                    return;
                m_Avatar = ScriptableObject.CreateInstance<DynamicBoneAvatar>();
                m_Avatar.name = NativeFileSystem.GetFileName(createFileAt, true);
            }
            DynamicBoneAvatar.UpdateAvatarAsset(m_Avatar, this);
            if (!string.IsNullOrEmpty(createFileAt))
            {
                if (File.Exists(createFileAt))
                    UnityEditor.AssetDatabase.AddObjectToAsset(m_Avatar, createFileAt);
                else
                    UnityEditor.AssetDatabase.CreateAsset(m_Avatar, createFileAt);
                UnityEditor.EditorUtility.SetDirty(m_Avatar);
                UnityEditor.EditorUtility.SetDirty(this);
                UnityEditor.AssetDatabase.ImportAsset(createFileAt);
            }
        }

        private void OnValidate()
        {
            MainThread.RunOnMainThread(MarkDirty, EExecuteEvent.LateUpdate);
        }

        void MarkDirty()
        {
            if (mNativeInst != null)
                DynamicBoneJobs.MarkRootDirty(mNativeInst, true);
        }

        private void OnTransformParentChanged()
        {
            if (mNativeInst != null)
                DynamicBoneJobs.MarkRootDirty(mNativeInst, true);
        }

        private void OnDrawGizmosSelected()
        {
            if (!enabled)
                return;
            Gizmos.color = Color.white;
            Gizmos.matrix = Matrix4x4.identity;
            if (Application.isPlaying && mNativeInst != null && mNativeInst.IsAllocated)
            {
                var num = mNativeInst.ParticleCount;
                for (int i = 0; i < num; i++)
                {
                    var p = mNativeInst.GetParticle(i);
                    //Gizmos.color = (p.flags & (byte)EDynamicParticleFlags.FixedOrientation) == 0 ? Color.green : Color.white;
                    if (p.radius >= 0.001f)
                        Gizmos.DrawWireSphere(p.position, p.radius);
                    if (p.parentIndex != -1)
                    {
                        var p0 = mNativeInst.GetParticle(p.parentIndex);
                        Gizmos.DrawLine(p.position, p0.position);
                    }
                }
                num = mNativeInst.ColliderCount;
                Gizmos.color = Color.gray;
                for (int i = 0; i < num; i++)
                {
                    var col = mNativeInst.GetCollider(i);
                    if (col.id != 0)
                    {
                        Gizmos.matrix = Matrix4x4.TRS(col.position, col.rotation, col.size);
                        Gizmos.DrawWireCube(Vector3.zero, Vector3.one);
                    }
                }
            }
            else
            {
                if (transform.hasChanged || m_Particles.Count == 0)
                {
                    EDynamicBoneFlags flag = 0;
                    float bone = 0;
                    DynamicBoneAvatar.SetupParticles(m_Particles, this, ref flag, ref bone);
                }
                var num = m_Particles.Count;
                for (int i = 0; i < num; i++)
                {
                    var p = m_Particles[i];
                    //Gizmos.color = (p.data.flags & (byte)EDynamicParticleFlags.FixedOrientation) == 0 ? Color.green : Color.white;
                    if (p.data.radius > 0.001f)
                        Gizmos.DrawWireSphere(p.transform.position, p.data.radius);
                    if (p.data.parentIndex != -1)
                    {
                        var p0 = m_Particles[p.data.parentIndex];
                        Gizmos.DrawLine(p0.transform.position, p.transform.position);
                    }
                }
            }
        }
#endif
    }
}